﻿using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace Server {
	class ServiceController {
		protected Dictionary<String, ServiceHost> serviceHosts;
		protected Dictionary<String, Type> services;
		protected Uri uri;
		private Boolean running = false;

		public ServiceController(String host, Decimal port) {
			setUri(host, port);

			services = new Dictionary<String, Type>();
			serviceHosts = new Dictionary<String, ServiceHost>();
		}

		public ServiceController(Decimal port)
			: this("localhost", port) {

		}

		public ServiceController()
			: this("localhost", 8000) {

		}

		private void setUri(String host, Decimal port) {
			uri = new Uri(Uri.UriSchemeNetTcp + "://" + host + ":" + port + "/wifiperf/");

			if (!host.Equals(uri.Host)) {
				throw new Exception("Host name is wrong");
			}
		}

		private void setUri(String host) {
			setUri(host, uri.Port);
		}

		private void setUri(Decimal port) {
			setUri(uri.Host, port);
		}

		public Uri GetUri() {
			return uri;
		}

		public Uri GetUri(String serviceName) {
			return new Uri(GetUri(), serviceName);
		}

		public void SetHost(String host) {
			setUri(host);
			if (IsRunning()) {
				Restart();
			}
		}

		public void SetPort(Decimal port) {
			setUri(port);
			if (IsRunning()) {
				Restart();
			}
		}

		public Boolean HasService(Type serviceType) {
			String name = serviceType.Name.ToString();
			return HasService(name);
		}

		public Boolean HasService(String serviceName) {
			return services.ContainsKey(serviceName);
		}

		public void AddService(Type serviceType) {
			String name = serviceType.Name.ToString();

			services.Add(name, serviceType);
		}

		public void RemoveService(Type serviceType) {
			String name = serviceType.Name.ToString();
			RemoveService(name);
		}

		public void RemoveService(String serviceName) {
			if (running && serviceHosts.ContainsKey(serviceName)) {
				ServiceHost service = serviceHosts[serviceName];

				service.Close();
				serviceHosts.Remove(serviceName);
			}

			services.Remove(serviceName);
		}

		public bool IsRunning() {
			return running;
		}

		public bool IsRunning(String serviceName) {
			if (!serviceHosts.ContainsKey(serviceName)) {
				throw new IndexOutOfRangeException();
			}

			ServiceHost service = serviceHosts[serviceName];

			return service.State == CommunicationState.Opened;
		}

		public void Restart() {
			Stop();
			Start();
		}

		public void Start() {
			if (running) {
				return;
			}

			if (serviceHosts.Count > 0) {
				Stop();
			}

			ServiceHost serviceHost;
			String name;
			Uri address;
			Type serviceType;

			foreach (KeyValuePair<String, Type> service in services) {
				serviceType = service.Value;
				name = service.Key;
				address = GetUri(name);

				if (serviceType.GetInterfaces().Length < 1) {
					throw new ServiceActivationException("Service hasn't proper interface");
				}

				Type implementedContract = serviceType.GetInterfaces()[0];

				// Step 2 of the hosting procedure: Create ServiceHost

				//serviceHost = new ServiceHost(serviceType);
				serviceHost = new ServiceHost(serviceType, address);

				try {
					NetTcpBinding binding = new NetTcpBinding();
					binding.CloseTimeout = TimeSpan.FromSeconds(10);
					binding.OpenTimeout = TimeSpan.FromSeconds(30);
					binding.ReceiveTimeout = TimeSpan.FromSeconds(120);
					binding.SendTimeout = TimeSpan.FromSeconds(120);

					binding.MaxConnections = 127;
					binding.ListenBacklog = 127;

					binding.ReliableSession.InactivityTimeout = TimeSpan.FromHours(12);
					binding.ReliableSession.Ordered = true;

					binding.Security.Mode = SecurityMode.Message;
					binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

					// Step 3 of the hosting procedure: Add a service endpoint.
					serviceHost.AddServiceEndpoint(
						implementedContract,
						binding,
						""
					);

					ServiceThrottlingBehavior stb = new ServiceThrottlingBehavior();
					stb.MaxConcurrentCalls = 127;
					stb.MaxConcurrentInstances = 127;
					stb.MaxConcurrentSessions = 127;
					serviceHost.Description.Behaviors.Add(stb);
					
					Logger.Info("Service '" + name + "' URI: " + address);

					// Step 4 of the hosting procedure: Enable metadata exchange.
					Uri serviceMetadataAddress = new Uri(Uri.UriSchemeHttp + "://localhost:" + (address.Port + 1) + address.AbsolutePath);
					ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
					smb.HttpGetUrl = serviceMetadataAddress;
					smb.HttpGetEnabled = true;
					serviceHost.Description.Behaviors.Add(smb);
					Logger.Info("Service Metadata for '" + name + "' URI: " + serviceMetadataAddress);

					// Step 5 of the hosting procedure: Start (and then stop) the service.
					serviceHost.Open();

					serviceHosts.Add(name, serviceHost);

					Logger.Debug("The service '" + name + "' is ready");
					running = true;

				} catch (CommunicationException ce) {
					Logger.Error(ce.Message);
					if (null != serviceHost) {
						serviceHost.Abort();
					}
				}
			}
		}

		public void Stop() {
			running = false;

			foreach (KeyValuePair<String, ServiceHost> service in serviceHosts) {
				if (service.Value.State == CommunicationState.Opened || service.Value.State == CommunicationState.Opening) {
					// Close the ServiceHostBase to shutdown the service.
					Logger.Debug("The service '" + service.Key + "' is stopping...");
					service.Value.Close();
				}
			}

			serviceHosts.Clear();
		}

		public Object GetInstance(Type serviceType) {
			String name = serviceType.Name.ToString();
			return GetInstance(name);
		}

		public Object GetInstance(String serviceName) {
			if (!serviceHosts.ContainsKey(serviceName)) {
				throw new IndexOutOfRangeException();
			}

			ServiceHost service = serviceHosts[serviceName];

			Debug.Assert(service.SingletonInstance != null);

			return service.SingletonInstance;
		}

		~ServiceController() {
			Stop();
		}
	}
}